from gi.repository import Unity, Gio, GObject, Dbusmenu
import sys
import os
import thread
import time
import argparse
from threading import Thread, current_thread, Lock
import google
from unity_pb2 import *
import pygtk, gtk
from array import array
import inspect
import pynotify

debug = True

class Logger:
	def __init__(self):
		if not debug: pass
		window = gtk.Window()
		window.set_title("UnityProxy Logger")
		window.set_default_size(640, 480)
		log = gtk.TextView()
		log.set_wrap_mode(gtk.WRAP_WORD)
		self.buffer = log.get_buffer()
		window.add(log)
		window.show_all()
	def log(self, message):
		if not debug: pass
		self.buffer.insert_at_cursor(str(message) + '\n')
		pass
	def append(self, message):
		if not debug: pass
		self.buffer.insert_at_cursor(message)
		pass
		
#class MainLoop(Thread):
#	def __init__(self):
#		Thread.__init__(self)
#		self.daemon(True)
#	def run(self):
#		gtk.main()

class ImplException(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr(self.value)

class GLoop(Thread):
	def __init__ (self):
		Thread.__init__(self)
		#self.daemon(True)
		self.loop = loop = GObject.MainLoop()	
	def run(self):
		GObject.threads_init()
		self.loop.run()

class PropertyType:
	INT = 'int'
	STRING = 'string'
	DOUBLE = 'double'
	BOOLEAN = 'bool'
	
class Byte:
	MAX = 255
	MIN = 0	
	

out_lock = Lock()
launchers = {} # by app name <string, LauncherEntry>
pynotify.init("Unity Java API")
quicklists = {} # by app name <string, map<int, Menuitem>>
#radio_groups = {} # by group id <int, list<Menuitem>>

#def get_radio_group(id):
#	try:
#		group = radio_groups[id]
#	except KeyError:
#		radio_groups[id] = group = []
#	return group
def get_launcher(app):
	try:
		return launchers[app]
	except KeyError:
		logger.log("preparing launcher for app: " + str(app))
		launchers[app] = cached = Unity.LauncherEntry.get_for_desktop_id(app)
		return cached

notifications = {}

def pushNotification(title, body, icon):
	#logger.log("pushing notification: " + title + " " + body + " " + icon)
	n = pynotify.Notification(title, body, icon)
	n.set_hint_string ("x-canonical-append", "true")
	n.show()
	return n

#def toggle_radio(item, num, group):
#	for other in group:
#		other.property_set_int('toggle-state', 0)
#	item.property_set_int('toggle-state', 1)

def fire_item_event(item, number, id):
	logger.log('item event fired: ' + str(id))
	res = ResponsePb()
	res.value.item.id = id
	send_response(res)

#def toggle_checkbox(item, num):
#	selected = item.property_get_int('toggle-state')
#	if selected:
#		item.property_set_int('toggle-state', 0)
#	else:
#		item.property_set_int('toggle-state', 1)

def prepare_item_from_proto(proto):
	item = Dbusmenu.Menuitem.new()
	
	if proto.HasField('label'):
		item.property_set ('label', proto.label)
	# default behavior for no label set
	else: item.property_set('label', "")
		
	if proto.HasField('enabled'):
		item.property_set_bool ('enabled', proto.enabled)
	
	if proto.HasField('visible'):
		item.property_set_bool('visible', proto.visible)
		
	if proto.HasField('selected'):
		item.property_set_int('toggle-state', int(proto.selected))
		
	if proto.HasField('type'):
		if proto.type == ItemPb.SEPARATOR:
			item.property_set('type', 'separator')
		elif proto.type == ItemPb.SELECT:
			item.property_set('toggle-type', 'checkmark')
		elif proto.type == ItemPb.RADIO:
			item.property_set('toggle-type', 'radio')
		
	return item

def handle(req, res):
	logger.append("request received: ")
	
	if req.type == RequestPb.GET:
		logger.append("GET \n")
		propertyType = get_property_type(req.property)
		name = get_property_name(req.property)
		setattr(res.value, propertyType, get_launcher(req.app).get_property(name))
		
	elif req.type == RequestPb.SET:
		logger.append("SET ")
		if req.property == QUICKLIST:
			logger.log("quicklist ")
			quicklist = Dbusmenu.Menuitem.new()
			items = {}
			for proto in req.value.quicklist.item:
				item = prepare_item_from_proto(proto)
				quicklist.child_append(item)
				if proto.HasField('id'):
					item.connect("item-activated", fire_item_event, proto.id)
					items[proto.id] = item
			get_launcher(req.app).set_property('quicklist', quicklist)
			quicklists[req.app] = items
		else:
			#every other property but not quicklist
			propertyType = get_property_type(req.property)
			name = get_property_name(req.property)
			logger.log(name)
			get_launcher(req.app).set_property(name, getattr(req.value, propertyType))
			
	elif req.type == RequestPb.NOTIFY:
		notifications[req.notification.id] = pushNotification(req.notification.title, req.notification.message, req.notification.icon)
	
	elif req.type == RequestPb.NOTIFY_UPDATE:
		try:
			n = notifications[req.notification.id]
			n.update(req.notification.title, req.notification.message, req.notification.icon)
			n.show()
		except KeyError:
			# TODO just make a new notification instead of propagating exception
			raise ImplException("notification to be updated does NOT exists")
		
	elif req.type == RequestPb.UPDATE_ITEM:
		
		logger.append("UPDATE_ITEM ")
		if not req.HasField('app'):
			raise ImplException("no app specified on update")
		quicklist = quicklists[req.app]
		
		if quicklist == None:
			raise ImplException("the quicklist does not exists on update")
		if not req.value.HasField('item'):
			raise ImplException("no item specified on update")
		if not req.value.item.HasField('id'):
			raise ImplException("specified item has no id on update")
		
		logger.log(req.value.item.id)
		
		item = quicklist[req.value.item.id]
		itemProto = req.value.item
		
		if itemProto.HasField('visible'):
			logger.log("after visible")
			item.property_set_bool('visible', itemProto.visible)
		elif itemProto.HasField('enabled'):
			item.property_set_bool('enabled', itemProto.enabled)
		elif itemProto.HasField('selected'):
			item.property_set_int('toggle-state', int(itemProto.selected))
		elif itemProto.HasField('label'):
			item.property_set('label', itemProto.label)
		else:
			raise ImplException("no attribute specified to update in quicklist item")
		
	elif req.type == RequestPb.INDICATOR_GET:
		raise ImplException("AppIndicators are not implemented yet")
	elif req.type == RequastPb.INDICATOR_SET:
		raise ImplException("AppIndicators are not implemented yet")
	else: raise ImplException("Unsupported request type:" + str(req.type))
	
def update_item(proto, item, defaults):
	pass
	
def send_response(pb):
	with out_lock:
		def write_size(size):
			if size >= Byte.MAX:
				for a in range(size / Byte.MAX):
					sys.stdout.write(chr(Byte.MAX))
				sys.stdout.write(chr(size % Byte.MAX))
			else: sys.stdout.write(chr(size))
		response = pb.SerializeToString()
		write_size(len(response))
		sys.stdout.write(response)
		sys.stdout.flush()
	
def receive_request():
	def read_size():
		size = 0
		while True:
			#logger.log("reading next size pointer")
			b = sys.stdin.read(1)
			if not b:
				logger.log("end of pipe reached, exiting program")
				#os._exit(1)
			#logger.log("just read next size pointer: " + str(ord(b)))
			p = int(str(ord(b)))
			size += p
			#logger.log("after merging size")
			if p < Byte.MAX:
				#logger.log("size is known so lets read the message: " + str(size)) 
				return size
			
	#logger.log("waiting for next request...")
	size = read_size()
	req = RequestPb()
	data = sys.stdin.read(size)
	req.ParseFromString(data)
	return req 

def get_property_type(property):
	if req.property == COUNT:
		return PropertyType.INT
	elif req.property == COUNT_VISIBLE:
		return PropertyType.BOOLEAN
	elif req.property == PROGRESS:
		return PropertyType.DOUBLE
	elif req.property == PROGRESS_VISIBLE:
		return PropertyType.BOOLEAN
	elif req.property == URGENT:
		return PropertyType.BOOLEAN
	elif req.property == APP_URI:
		return PropertyType.STRING
	else: raise ImplException("Unknown launcher property type (value type)")
	
def get_property_name(propertyType):
	if propertyType == COUNT:
		return 'count'
	elif propertyType == COUNT_VISIBLE:
		return 'count-visible'
	elif propertyType == PROGRESS:
		return 'progress'
	elif propertyType == PROGRESS_VISIBLE:
		return 'progress-visible'
	elif propertyType == URGENT:
		return 'urgent'
	elif propertyType == QUICKLIST:
		return 'quicklist'
	elif propertyType == APP_URI:
		return 'app-uri'
	else: raise ImplException("Unknown launcher property, cannot get it's name")
	
if __name__ == '__main__':
	#sys.stdin = open("file.txt")
	logger = Logger()
	gloop = GLoop()
	gloop.start()
	logger.log("sending check byte: " + str(47))
	sys.stdout.write(chr(47))
	b = sys.stdin.read(1)
	c = ord(b)
	logger.log("check byte: " + str(c))
	while True:
		try:
			req = receive_request()
			res = ResponsePb()
			handle(req, res)
			send_response(res)
		except ImplException, e:
			errorMessage = ResponsePb()
			errorMessage.result = ResponsePb.FAILURE
			errorMessage.error_message = str(e)
			errorMessage.impl = True
			logger.log("impl exception, sending error response: " + str(errorMessage)) 
			send_response(errorMessage)
		except Exception, e:
			errorMessage = ResponsePb()
			errorMessage.result = ResponsePb.FAILURE
			errorMessage.error_message = str(e)
			logger.log("error occurred, sending error response: " + str(errorMessage)) 
			send_response(errorMessage)
				
	#else:
	#	errorMessage = ResponsePb()
	#	errorMessage.result = ResponsePb.FAILURE
	#	errorMessage.error_message = 'Initialization exception'
	#	send_response(errorMessage)



